package com.kamistoat.ruoyi.ruoyimoduleslocalfile.utils;

import com.kamistoat.ruoyi.ruoyicommoncore.exception.file.FileNameLengthLimitExceededException;
import com.kamistoat.ruoyi.ruoyicommoncore.exception.file.FileSizeLimitExceededException;
import com.kamistoat.ruoyi.ruoyicommoncore.exception.file.InvalidExtensionException;
import com.kamistoat.ruoyi.ruoyicommoncore.utils.DateUtils;
import com.kamistoat.ruoyi.ruoyicommoncore.utils.StringUtils;
import com.kamistoat.ruoyi.ruoyicommoncore.utils.file.FileTypeUtils;
import com.kamistoat.ruoyi.ruoyicommoncore.utils.file.MimeTypeUtils;
import com.kamistoat.ruoyi.ruoyicommoncore.utils.uuid.Seq;
import org.apache.commons.io.FilenameUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.util.Objects;

/**
 * 文件上传的工具类。
 * 提供针对所有持久化算法的环绕处理(例如处理保存路径等等)，以及Spring原生持久化算法的核心方法
 */
public class FileUploadUtils {
    /**
     * 默认文件大小 50M
     */
    public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024;

    /**
     * 默认的文件名最大长度 100
     */
    public static final int DEFAULT_FILE_NAME_LENGTH = 100;

    /**
     * 使用 Spring原生持久化算法实现文件持久化。
     * 将文件持久化到输入的路径中。
     *
     * @param baseDir       文件持久化的基目录。可以理解为数据库的库, 例如 /home/kamistoat/ruoyi
     * @param multipartFile 要上传的文件
     * @return 返回上传成功的文件路径(排除根目录)
     */
    public static String upload(String baseDir, MultipartFile multipartFile) throws IOException {
        try {
            return upload(baseDir, multipartFile, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
        } catch (Exception e) {
            throw new IOException(e.getMessage(), e);
        }
    }


    /**
     * 使用 Spring原生持久化算法实现文件持久化。
     * 对上传文件进行验证，验证成功后将文件持久化到输入的路径中。
     * 在上面的 upload方法中被调用
     *
     * @param baseDir          相对应用的根目录。可以理解为数据库中的库，例如 /home/kamistoat/ruoyi
     * @param multipartFile    上传的文件
     * @param allowedExtension 允许上传/下载的媒体类型(即扩展名)
     * @return 返回上传成功的文件路径(排除根目录)
     * @throws FileSizeLimitExceededException       如果超出最大大小
     * @throws FileNameLengthLimitExceededException 文件名太长
     * @throws IOException                          比如读写文件出错时
     * @throws InvalidExtensionException            文件校验异常
     */
    public static String upload(String baseDir, MultipartFile multipartFile, String[] allowedExtension)
            throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException, InvalidExtensionException {
        // 调用 MultipartFile.getgetOriginalFilename() 获取文件的真实名称。返回 文件名称+文件扩展名
        int fileNameLength = Objects.requireNonNull(multipartFile.getOriginalFilename()).length();
        if (fileNameLength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH) {
            throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH);
        }

        // 文件大小校验，并校验文件扩展名是否为系统所允许
        assertAllowed(multipartFile, allowedExtension);

        // 编码文件名，编码后文件名为 日期/原文件名_UUID.原文件扩展名
        // 结果示例: 2022/09/09/test_uuid.png
        String codingFileName = extractFilename(multipartFile);

        // 在要持久化文件的路径下创建 File指针，准备开始持久化 MultipartFile
        // 绝对路径下的 File指针示例: /home/kamistoat/ruoyi/2022/09/09/test_uuid.png
//        String absPath = getAbsoluteFile(baseDir, codingFileName).getAbsolutePath();
//        multipartFile.transferTo(Paths.get(absPath));
        File absoluteFile = getAbsoluteFile(baseDir, codingFileName);
        multipartFile.transferTo(absoluteFile);

        // 返回上传成功的文件路径(排除根目录): /2022/09/09/test_uuid.png
        return "/" + codingFileName;
    }

    /**
     * 编码文件名。编码后文件名为 日期/原文件名_UUID.原文件扩展名
     * 因此上传的文件将以日期为文件夹被分开
     *
     * @param multipartFile 上传的文件
     * @return 编码后的文件名
     */
    public static String extractFilename(MultipartFile multipartFile) {
        return StringUtils.format("{}/{}_{}.{}", DateUtils.datePath(),
                FilenameUtils.getBaseName(multipartFile.getOriginalFilename()), Seq.getId(Seq.uploadSeqType), FileTypeUtils.getExtension(multipartFile));
    }

    /**
     * 在要持久化文件的路径下创建 File指针
     *
     * @param baseDir        文件持久化的基目录
     * @param codingFileName 文件名称
     * @return File指针
     */
    public static File getAbsoluteFile(String baseDir, String codingFileName) throws IOException {
        // 库方法 File.separator 可以自动判断操作系统，获取当前操作系统下的 文件路径分隔符
        File fileP = new File(baseDir + File.separator + codingFileName);

        // 创建文件夹。
        if (!fileP.exists()) {
            if (!fileP.getParentFile().exists()) {
                boolean mkdirs = fileP.getParentFile().mkdirs();
                if (!mkdirs) {
                    throw new IOException("无法创建文件:" + baseDir + File.separator + codingFileName);
                }
            }
        }
        // 返回绝对路径下的文件指针
        return fileP.isAbsolute() ? fileP : fileP.getAbsoluteFile();
    }

    /**
     * 校验文件大小，并校验文件扩展名是否为系统所允许
     *
     * @param multipartFile    上传的文件
     * @param allowedExtension 允许上传/下载的媒体类型(即扩展名)
     * @throws FileSizeLimitExceededException 文件超出大小限制
     * @throws InvalidExtensionException      文件扩展名异常
     */
    public static void assertAllowed(MultipartFile multipartFile, String[] allowedExtension)
            throws FileSizeLimitExceededException, InvalidExtensionException {
        // 使用 MultipartFile.getSize() 获取文件的字节大小，验证文件大小
        long size = multipartFile.getSize();
        if (size > DEFAULT_MAX_SIZE) {
            throw new FileSizeLimitExceededException(DEFAULT_MAX_SIZE / 1024 / 1024);
        }

        // 获取上传文件的完整名称
        String originalFilename = multipartFile.getOriginalFilename();
        // 获取文件扩展名
        String extension = FileTypeUtils.getExtension(multipartFile);

        // 若不允许上传/下载任何类型 或者 当前上传文件类型不再允许范围内。
        // 则根据允许列表抛出异常
        if (allowedExtension != null && !isAllowedExtension(extension, allowedExtension)) {
            if (allowedExtension == MimeTypeUtils.IMAGE_EXTENSION) {
                // 只允许图片且规定扩展
                throw new InvalidExtensionException.InvalidImageExtensionException(allowedExtension, extension,
                        originalFilename);
            } else if (allowedExtension == MimeTypeUtils.FLASH_EXTENSION) {
                // 只允许Flash且规定扩展
                throw new InvalidExtensionException.InvalidFlashExtensionException(allowedExtension, extension,
                        originalFilename);
            } else if (allowedExtension == MimeTypeUtils.MEDIA_EXTENSION) {
                throw new InvalidExtensionException.InvalidMediaExtensionException(allowedExtension, extension,
                        originalFilename);
            } else if (allowedExtension == MimeTypeUtils.VIDEO_EXTENSION) {
                throw new InvalidExtensionException.InvalidVideoExtensionException(allowedExtension, extension,
                        originalFilename);
            } else {
                // 允许多种扩展
                throw new InvalidExtensionException(allowedExtension, extension, originalFilename);
            }
        }
    }

    /**
     * 判断上传的文件类型 是否位于 规定的允许上传/下载的文件类型数组中(即扩展名是否允许)
     *
     * @param extension        当前上传文件的扩展名
     * @param allowedExtension 提前规定的允许下载/上传的扩展名数组
     * @return 是否允许
     */
    public static boolean isAllowedExtension(String extension, String[] allowedExtension) {
        for (String allowedOne : allowedExtension) {
            if (StringUtils.equalsIgnoreCase(extension, allowedOne)) {
                return true;
            }
        }
        return false;
    }
}
























